#ifndef DW_GRAFFITI_HARDWARE_BUFFER_H
#	define DW_GRAFFITI_HARDWARE_BUFFER_H

#include "enums.h"

namespace dw {
	namespace graffiti {

template< GLenum bindingType >
struct HardwareBuffer
{
public : //! @name Commands
	//! @{
	void bind()
	{
		glBindBuffer(bindingType, m_id);
	}
	
	void unbind()
	{
		glBindBuffer(bindingType, 0);
	}

	void setData(const void* data)
	{
		dwAssert(data != NULL, "Invalid parameter: data is null");
		dwAssert(m_size != 0, "Invalid call to setData: size has not been set");
		dwAssert(isValid(), "Invalid call to setData: buffer is not valid");
		
		glBufferSubData(bindingType, 0, m_size, data);
	}
	//! @}

public : //! @name State
	//! @{
	bool isValid() const
	{
		return m_id != 0 && glIsBuffer(m_id) == GL_TRUE;
	}
	//! @}
	
protected : //! @name Internal commands
	//! @{
	void resize(uint32 sizeInBytes)
	{
		dwAssert(isValid(), "Invalid call to setData: buffer is not valid");

		m_size = sizeInBytes;

		glBufferData(bindingType, m_size, NULL, m_usage);
	}

	void resize(uint32 elementSize, uint32 count)
	{
		resize(elementSize * count);
	}
	
	void resize(attribute::type::types elementType, uint32 count)
	{
		resize(attribute::type::size(elementType) * count);
	}
	
	void setSubData(uint32 offsetInBytes, void* data, uint32 sizeInBytes)
	{
		dwAssert(data != NULL, "Invalid parameter: data is null");
		dwAssert(offsetInBytes + sizeInBytes <= m_size, "Invalid parameter: offsetInBytes + sizeInBytes > m_size");
		dwAssert(isValid(), "Invalid call to setData: buffer is not valid");
		
		glBufferSubData(bindingType, offsetInBytes, sizeInBytes, data);
	}
	
	void setSubData(uint32 elementSize, uint32 elementIndex, void* data, uint32 elementCount)
	{
		setSubData(elementSize * elementIndex, data, elementSize * elementIndex);
	}
	
	void setSubData(attribute::type::types elementType, uint32 elementIndex, void* data, uint32 elementCount)
	{
		setSubData(attribute::type::size(elementType), elementIndex, data, elementCount);
	}
	//! @}

protected : //! @name Construction & Destruction
	//! @{
	HardwareBuffer(buffer::update::frequency frequency, buffer::access::type access)
	:	m_usage(buffer::glUsage(frequency, access))
	,	m_size(0)
	{
		glGenBuffers(1, &m_id);
	}

	HardwareBuffer(GLuint id, buffer::update::frequency frequency, buffer::access::type access)
	:	m_id(id)
	,	m_usage(buffer::glUsage(frequency, access))
	,	m_size(0)
	{
		dwAssert(m_id != 0, "Invalid parameter: id is 0");
	}

	~HardwareBuffer()
	{
		glDeleteBuffers(1, &m_id);
	}
	//! @}

protected : //! @name Data
	//! @{
	GLuint m_id;
	GLenum m_usage;
	uint32 m_size;
	//! @}

private : //! @name Copy protection
	//! @{
	HardwareBuffer(const HardwareBuffer& ref);
	//! @}
};

	} // namespace graffiti
} // namespace dw

#endif // !DW_GRAFFITI_HARDWARE_BUFFER_H
